﻿namespace Yaaf.WP7.NativeAccess.ComWrapper.AllDevices.COM_NativeIO
{
    using System;
    using System.Runtime.InteropServices;

    using Yaaf.WP7.NativeAccess.Helper;

    [ComImport, Guid("BD4D0C42-91D1-44C3-86B0-4447FDF82BCE"),
       ClassInterface(ClassInterfaceType.None)]
    internal class FileSystemClass { }

    [StructLayout(LayoutKind.Sequential)]
    internal struct FILETIME
    {
        public uint dwLowDateTime;
        public uint dwHighDateTime;
    };

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    internal struct WIN32_FIND_DATA
    {
        public FileAttributes dwFileAttributes;
        public FILETIME ftCreationTime;
        public FILETIME ftLastAccessTime;
        public FILETIME ftLastWriteTime;
        public int nFileSizeHigh;
        public int nFileSizeLow;
        public int dwReserved0;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;

        public long GetSize()
        {
            return ((long)this.nFileSizeHigh << 32) & this.nFileSizeLow;
        }
    }

    [ComImport, Guid("47A34768-AA01-4365-BB55-386BBB0FBBF6"),
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IFileSystem
    {
        [PreserveSig]
        int OpenFile(string lpFilename, int dwDesiredAccess, int dwShareMode, int dwCreationDisposition, int dwFlagsAndAttributes, out IntPtr hFile);

        [PreserveSig]
        int ReadFile(IntPtr hfile, IntPtr lpBuffer, int nNumberOfBytesToRead, out int lpNumberOfBytesRead);

        [PreserveSig]
        int CloseFile(IntPtr hFile);

        [PreserveSig]
        int SeekFile(IntPtr hFile, int lDistanceToMove, ref int lpDistanceToMoveHigh, int dwMoveMethod);

        [PreserveSig]
        int GetFileSize(IntPtr hFile, out int lpFileSizeHigh);

        [PreserveSig]
        int CopyFile(string lpExistingFileName, string lpNewFileName, bool bFailIfExists);

        [PreserveSig]
        int FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData, out IntPtr hFind);

        [PreserveSig]
        int FindNextFile(IntPtr hFind, out WIN32_FIND_DATA lpFindFileData);

        [PreserveSig]
        int FindClose(IntPtr hFind);
    }

    [Flags]
    public enum FileAccess : uint
    {
        /// <summary>
        /// Read file access
        /// </summary>
        Read = 0x80000000,
        /// <summary>
        /// Write file acecss
        /// </summary>
        Write = 0x40000000,
        /// <summary>
        /// Execute file access
        /// </summary>
        Execute = 0x20000000,
        /// <summary>
        /// 
        /// </summary>
        All = 0x10000000
    }

    [Flags]
    public enum FileShare : uint
    {
        /// <summary>
        /// 
        /// </summary>
        None = 0x00000000,
        /// <summary>
        /// Enables subsequent open operations on an object to request read access. 
        /// Otherwise, other processes cannot open the object if they request read access. 
        /// If this flag is not specified, but the object has been opened for read access, the function fails.
        /// </summary>
        Read = 0x00000001,
        /// <summary>
        /// Enables subsequent open operations on an object to request write access. 
        /// Otherwise, other processes cannot open the object if they request write access. 
        /// If this flag is not specified, but the object has been opened for write access, the function fails.
        /// </summary>
        Write = 0x00000002,
        /// <summary>
        /// Enables subsequent open operations on an object to request delete access. 
        /// Otherwise, other processes cannot open the object if they request delete access.
        /// If this flag is not specified, but the object has been opened for delete access, the function fails.
        /// </summary>
        Delete = 0x00000004
    }

    public enum CreationDisposition : uint
    {
        /// <summary>
        /// Creates a new file. The function fails if a specified file exists.
        /// </summary>
        New = 1,
        /// <summary>
        /// Creates a new file, always. 
        /// If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes, 
        /// and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies.
        /// </summary>
        CreateAlways = 2,
        /// <summary>
        /// Opens a file. The function fails if the file does not exist. 
        /// </summary>
        OpenExisting = 3,
        /// <summary>
        /// Opens a file, always. 
        /// If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW.
        /// </summary>
        OpenAlways = 4,
        /// <summary>
        /// Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist.
        /// The calling process must open the file with the GENERIC_WRITE access right. 
        /// </summary>
        TruncateExisting = 5
    }

    [Flags]
    public enum FileAttributes : uint
    {
        Readonly = 0x00000001,
        Hidden = 0x00000002,
        System = 0x00000004,
        Directory = 0x00000010,
        Archive = 0x00000020,
        Device = 0x00000040,
        Normal = 0x00000080,
        Temporary = 0x00000100,
        SparseFile = 0x00000200,
        ReparsePoint = 0x00000400,
        Compressed = 0x00000800,
        Offline = 0x00001000,
        NotContentIndexed = 0x00002000,
        Encrypted = 0x00004000,
        WriteThrough = 0x80000000,
        Overlapped = 0x40000000,
        NoBuffering = 0x20000000,
        RandomAccess = 0x10000000,
        SequentialScan = 0x08000000,
        DeleteOnClose = 0x04000000,
        BackupSemantics = 0x02000000,
        PosixSemantics = 0x01000000,
        OpenReparsePoint = 0x00200000,
        OpenNoRecall = 0x00100000,
        FirstPipeInstance = 0x00080000
    }

    internal enum MoveMethod : uint
    {
        Begin = 0,
        Current = 1,
        End = 2
    }

    [StructLayout(LayoutKind.Explicit, Size = 8)]
    internal struct LARGE_INTEGER
    {
        [FieldOffset(0)]
        public Int64 QuadPart;
        [FieldOffset(0)]
        public Int32 LowPart;
        [FieldOffset(4)]
        public Int32 HighPart;
    }

    internal static class NativeIODllInterop
    {
        private static IFileSystem m_fileSystemIo;
        private const int INVALID_FILE_SIZE = unchecked((int)0xffffffff);
        private const int INVALID_SET_FILE_POINTER = -1;
        private const int ERROR_ACCESS_DENIED = unchecked((int)0x80000005);

        public static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

        static NativeIODllInterop()
        {
        }

        public static void Initialize()
        {
            // Just to call the static Constructor
            InteropHelper.RegisterDll("COM_NativeIO.dll", "BD4D0C42-91D1-44C3-86B0-4447FDF82BCE");
            m_fileSystemIo = new FileSystemClass() as IFileSystem;
        }

        public static int OpenFile(string lpFilename, FileAccess dwDesiredAccess, FileShare dwShareMode, CreationDisposition dwCreationDisposition, FileAttributes dwFlagsAndAttributes, out IntPtr hFile)
        {
            int ret = m_fileSystemIo.OpenFile(lpFilename, (int)dwDesiredAccess, (int)dwShareMode, (int)dwCreationDisposition, (int)dwFlagsAndAttributes, out hFile);
            return ret;
        }

        public static int ReadFile(IntPtr hfile, byte[] buffer, int nNumberOfBytesToRead, int offset, out int lpNumberOfBytesRead)
        {
            var bufferHandle = Microsoft.Phone.InteropServices.GCHandle.Alloc(buffer, GCHandleType.Pinned);

            int ret = m_fileSystemIo.ReadFile(hfile, new IntPtr(bufferHandle.AddrOfPinnedObject().ToInt32() + offset),
                                              nNumberOfBytesToRead,
                                              out lpNumberOfBytesRead);

            bufferHandle.Free();

            if (ret == 0)
            {
                int err = Microsoft.Phone.InteropServices.Marshal.GetLastWin32Error();

                if (err == ERROR_ACCESS_DENIED)
                {
                    throw new UnauthorizedAccessException("Access denied");
                }
            }

            return ret;
        }

        public static int CloseFile(IntPtr hFile)
        {
            return m_fileSystemIo.CloseFile(hFile);
        }

        public static int SeekFile(IntPtr hFile, long lDistanceToMove, MoveMethod dwMoveMethod)
        {
            var li = new LARGE_INTEGER();

            li.QuadPart = lDistanceToMove;
            var ret = m_fileSystemIo.SeekFile(hFile, li.LowPart, ref li.HighPart, (int)dwMoveMethod);

            if (ret == INVALID_SET_FILE_POINTER)
                throw new Exception("Invalid seek");

            return ret;
        }

        public static long GetFileSize(IntPtr hFile)
        {
            var li = new LARGE_INTEGER();
            li.LowPart = m_fileSystemIo.GetFileSize(hFile, out li.HighPart);

            if (li.LowPart == INVALID_FILE_SIZE)
                throw new Exception("Invalid file size");

            return li.QuadPart;
        }

        public static int CopyFile(string sourceFilename, string destinationFilename, bool failIfExists)
        {
            int ret = m_fileSystemIo.CopyFile(sourceFilename, destinationFilename, failIfExists);

            if (ret == 0)
            {
                int err = Microsoft.Phone.InteropServices.Marshal.GetLastWin32Error();

                if (err == ERROR_ACCESS_DENIED)
                {
                    throw new UnauthorizedAccessException("Access denied");
                }
            }

            return ret;
        }

        public static int FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData, out IntPtr hFind)
        {
            return m_fileSystemIo.FindFirstFile(lpFileName, out lpFindFileData, out hFind);
        }

        public static int FindNextFile(IntPtr hFind, out WIN32_FIND_DATA lpFindFileData)
        {
            return m_fileSystemIo.FindNextFile(hFind, out lpFindFileData);
        }

        public static int FindClose(IntPtr hFind)
        {
            return m_fileSystemIo.FindClose(hFind);
        }

    }
}
